package com.chuangjian.hire.order.service.impl;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chuangjian.hire.config.WxPayConfig;
import com.chuangjian.hire.order.dto.WechatConstant;
import com.chuangjian.hire.order.service.WechatService;
import com.chuangjian.hire.users.dto.AccessTokenDTO;
import com.chuangjian.hire.users.dto.TicketResponseDTO;
import com.chuangjian.hire.users.dto.initJSSDKDataDTO;
import com.chuangjian.hire.users.service.CacheService;
import com.chuangjian.hire.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class WechatServiceImpl implements WechatService {


    OkHttpClient client = new OkHttpClient();

    @Resource
    WxPayConfig wxPayConfig;

    @Resource
    private CacheService cacheService;

    private String getAccessToken(WxPayConfig wxPayConfig) {
        return getAccessToken(wxPayConfig.getAppId(), wxPayConfig.getAppSecret());
    }

    private String getTicket(WxPayConfig wxPayConfig) {
        return getTicket(getAccessToken(wxPayConfig), wxPayConfig);
    }

    private String getTicketKey(String appId) {
        return WechatConstant.WECHAT_OFFICIAL_TICKET_KEY + ":" + appId;
    }

    private String getTokenKey(String appId) {
        return WechatConstant.WECHAT_OFFICIAL_ACCESS_TOKEN_KEY + ":" + appId;
    }

    @Override
    public Boolean deleteTokenAndTicket() {
        cacheService.deleteCache(getTicketKey(wxPayConfig.getAppId()));
        cacheService.deleteCache(getTokenKey(wxPayConfig.getAppId()));
        return true;
    }


    private String getTicket(String accessToken, WxPayConfig wxPayConfig) {
        String ticket = cacheService.getCache(getTicketKey(wxPayConfig.getAppId()));
        if (StringUtil.isNotBlank(ticket)) {
            return ticket;
        }
        try {
            TicketResponseDTO ticketResp = getTicket(accessToken);
            //为什么会出现这个问题呢 应该正式环境，测试环境，的缓存redis都不一样 不能使用缓存
            if (Integer.valueOf("40001").equals(ticketResp.getErrCode())) {
                ticketResp = getTicket(refreshStoreOfAccessToken(wxPayConfig));
            }
            if (StringUtil.isNotBlank(ticketResp.getTicket())) {
                cacheService.setCacheForTimeOut(getTicketKey(wxPayConfig.getAppId()), ticketResp.getTicket(), Convert.toLong(ticketResp.getExpiresIn() - 200), TimeUnit.SECONDS);
            } else {
                log.error("获取Ticket出现错误：{} : {}", ticketResp.getErrCode(), ticketResp.getErrMsg());
            }
            ticket = ticketResp.getTicket();
        } catch (IOException e) {
            log.error("微信获取ticket失败", e);
        }
        return ticket;
    }

    private String refreshStoreOfAccessToken(WxPayConfig wxPayConfig) {
        cacheService.deleteCache(getTokenKey(wxPayConfig.getAppId()));
        return getAccessToken(wxPayConfig);
    }

    private TicketResponseDTO getTicket(String accessToken) throws IOException {
        String ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
        Request.Builder reqBuild = new Request.Builder();
        HttpUrl.Builder urlBuilder = HttpUrl.parse(ticketUrl).newBuilder();
        urlBuilder.addQueryParameter("access_token", accessToken);
        urlBuilder.addQueryParameter("type", "jsapi");
        reqBuild.url(urlBuilder.build());
        Response response = client.newCall(reqBuild.build()).execute();
        String string = response.body().string();
        return JSONObject.parseObject(string, TicketResponseDTO.class);
    }


    private String getAccessToken(String appId, String secret) {
        String accessToken = cacheService.getCache(getTokenKey(appId));
        if (StringUtils.isNotBlank(accessToken)) {
            return accessToken;
        }
        try {
            AccessTokenDTO token = getToken(appId, secret);
            cacheService.setCacheForTimeOut(getTokenKey(appId), token.getAccessToken(), Convert.toLong(token.getExpiresIn() - 100), TimeUnit.SECONDS);
            accessToken = token.getAccessToken();
        } catch (IOException e) {
            log.error("微信获取token失败", e);
        }
        return accessToken;
    }


    private AccessTokenDTO getToken(String appId, String secret) throws IOException {
        String access_token_url = "https://api.weixin.qq.com/cgi-bin/token";
        Request.Builder reqBuild = new Request.Builder();
        HttpUrl.Builder urlBuilder = HttpUrl.parse(access_token_url).newBuilder();
        urlBuilder.addQueryParameter("grant_type", WechatConstant.WECHAT_OFFICE_GRANT_TYPE);
        urlBuilder.addQueryParameter("appid", appId);
        urlBuilder.addQueryParameter("secret", secret);
        reqBuild.url(urlBuilder.build());

        Response response = client.newCall(reqBuild.build()).execute();
        String string = response.body().string();
        log.info(string);
        return JSONObject.parseObject(string, AccessTokenDTO.class);
    }

    @Override
    public initJSSDKDataDTO getInitJSSDKData(String url) {

        String currTime = String.valueOf(System.currentTimeMillis() / 1000);
        String noncestr = String.valueOf(System.currentTimeMillis());
        TreeMap<String, String> sign = new TreeMap<>();
        sign.put("jsapi_ticket", getTicket(wxPayConfig));
        sign.put("noncestr", noncestr);
        sign.put("timestamp", currTime);
        sign.put("url", url);

        String s = SHA1(map2param(sign));
        initJSSDKDataDTO si = new initJSSDKDataDTO();

        si.setAppId(wxPayConfig.getAppId());
        si.setNonceStr(noncestr);
        si.setTimestamp(currTime);
        si.setSignature(s);
        return si;
    }


    @Override
    public AccessTokenDTO getUserOpenId(String code) {
        String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
        Request.Builder reqBuild = new Request.Builder();
        HttpUrl.Builder urlBuilder = HttpUrl.parse(access_token_url).newBuilder();
        urlBuilder.addQueryParameter("appid", wxPayConfig.getAppId());
        urlBuilder.addQueryParameter("secret", wxPayConfig.getAppSecret());
        urlBuilder.addQueryParameter("code", code);
        urlBuilder.addQueryParameter("grant_type", "authorization_code");
        reqBuild.url(urlBuilder.build());
        try {
            Response response = client.newCall(reqBuild.build()).execute();
            String responses = response.body().string();
            log.info(responses);
            return JSON.parseObject(responses, AccessTokenDTO.class);
        } catch (IOException e) {
            log.error("获取openId错误", e);
        }
        return null;
    }

    private String map2param(Map<String, String> param) {
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, String> entry : param.entrySet()) {
            builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        return builder.substring(0, builder.length() - 1);
    }


    private final static String SHA1(String decript) {
        try {
            MessageDigest digest = MessageDigest
                    .getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
}
